package org.adl.registry.types;

import java.io.FileNotFoundException;
import java.io.IOException;
import net.handle.api.HSAdapter;
import net.handle.hdllib.Encoder;
import net.handle.hdllib.HandleException;
import net.handle.hdllib.HandleValue;
import net.handle.hdllib.Util;
import org.adl.registry.handles.HandleUtil;

/**
 * Provides methods for performing Handle System and website operations related to a Contributor.
 * @author Jacob Marks - Joint ADL Co-Lab
 * @version 1.0
 * @license This software is licensed as open source software pursuant to the terms defined in the Joint ADL Co-Lab Open Source Software License.  Use of any part of this software constitutes acceptance of this license. The Joint ADL Co-Lab open source agreement is available at {@link http://adlregistry.adlnet.gov/source_license.html}.
 */
public class Contributor
{
    private HSAdapter mHandleApi;
    
    private Username mUsername;
    private Password mPassword;
    
    private Handle mOperationalUserHandle;
    private Handle mPracticeUserHandle;
    
    private String mOperationalHandleName;
    private String mPracticeHandleName;
    
    private boolean mIsAuthenticatedForPractice;
    private boolean mIsAuthenticatedForOperational;
    
    /**
     * Creates a new instance of the Contributor class.
     * @param iUsername The <CODE>String</CODE> username of the Contributor.
     * @param iPassword The <CODE>String</CODE> password of the Contributor.
     * @throws java.io.FileNotFoundException Throws <CODE>FileNotFoundException</CODE> if the private key file for the website Handle System service account cannot be found on the local file system.
     * @throws java.io.IOException Throws <CODE>IOException</CODE> if the private key file for the website Handle System service account cannot be accessed.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if a new <CODE>HSAdapter</CODE> Handle System api object cannot be instantiated.
     */
    public Contributor(String iUsername, String iPassword)
    throws FileNotFoundException, IOException, HandleException, Exception
    {
        this.mHandleApi = HandleUtil.getHandleApi();
        
        this.mUsername = new Username(iUsername);
        this.mPassword = new Password(iPassword);
        
        this.getHandles();
    }
    
    /**
     * Creates a new instance of the Contributor class using the provided Handle System api.
     * @param iUsername The <CODE>String</CODE> username of the Contributor.
     * @param iPassword The <CODE>String</CODE> password of the Contributor.
     * @param iApi An <CODE>HSAdapter</CODE> for accessing the Handle System api.
     */
    public Contributor(String iUsername, String iPassword, HSAdapter iApi)
    throws Exception
    {
        this.mHandleApi = iApi;
        
        this.mUsername = new Username(iUsername);
        this.mPassword = new Password(iPassword);
        
        this.getHandles();
    }
    
    /**
     * Given a username and password pair, validates that a users credentials are valid.
     * @throws java.lang.Exception Throws <CODE>Exception</CODE> if an error occurs while encrypting the new password value using SHA encryption.
     */
    public void authenticate()
    throws Exception
    {
        String challengePassword = this.mPassword.getParameterValue();
        
        byte[] encodedUserProvidedPassword = Encoder.encodeSecretKey(challengePassword.getBytes(), true);
        
        if (this.mOperationalUserHandle.getHandleExists())
        {
            HandleValue secretKey = new HandleValue();
            
            HandleValue[] values = this.mOperationalUserHandle.getHandleValues();
            
            for (int i = 0; i < values.length; i++)
            {
                HandleValue value = values[i];
                
                if (value.getIndex() == 301)
                {
                    secretKey = value;
                    
                    break;
                }
            }
            
            byte[] encodedUserPassword = secretKey.getData();
            
            if (Util.equals(encodedUserProvidedPassword, encodedUserPassword))
            {
                this.mIsAuthenticatedForOperational = true;
            }
        }
        
        if (this.mPracticeUserHandle.getHandleExists())
        {
            HandleValue secretKey = new HandleValue();
            
            HandleValue[] values = this.mPracticeUserHandle.getHandleValues();
            
            for (int i = 0; i < values.length; i++)
            {
                HandleValue value = values[i];
                
                if (value.getIndex() == 301)
                {
                    secretKey = value;
                    
                    break;
                }
            }
            
            byte[] encodedUserPassword = secretKey.getData();
            
            if (Util.equals(encodedUserProvidedPassword, encodedUserPassword))
            {
                this.mIsAuthenticatedForPractice = true;
            }
        }
    }
    
    /**
     * Updates the password stored in the contributors user handles.
     * @param iNewPassword The <CODE>String</CODE> value to set as the new password.
     * @throws net.handle.hdllib.HandleException Throws <CODE>HandleException</CODE> if the Handle System encounters an error during Handle update.
     * @throws java.lang.Exception Throws <CODE>Exception</CODE> if an error occurs while encrypting the new password value using SHA encryption.
     */
    public void updatePassword(String iNewPassword)
    throws HandleException, Exception, PasswordConformanceException
    {
        Password newPass = new Password(iNewPassword);
        
        if (newPass.isParameterValueValid())
        {
            if (this.mOperationalUserHandle.getHandleExists())
            {
                HandleUtil.updateSecretKey(this.mOperationalHandleName, iNewPassword, this.mHandleApi);
            }
            
            if (this.mPracticeUserHandle.getHandleExists())
            {
                HandleUtil.updateSecretKey(this.mPracticeHandleName, iNewPassword, this.mHandleApi);
            }
        }
        else
        {
            throw new PasswordConformanceException();
        }
    }
    
    /**
     * Gets the operational registry user handle name based on the username provided.
     * @return Returns the <CODE>String</CODE> operational registry user handle name based on the username provided at <CODE>Contributor</CODE> instantiation.
     */
    public String getOperationalUserHandleName()
    {
        String username = this.mUsername.getParameterValue();
        
        String handle = Handle.FormatHandle(Username.OR_USER_HANDLE_NAMESPACE, username);
        
        return handle;
    }
    
    /**
     * Gets the practice registry user handle name based on the username provided.
     * @return Returns the <CODE>String</CODE> practice registry user handle name based on the username provided at <CODE>Contributor</CODE> instantiation.
     */
    public String getPracticeUserHandleName()
    {
        String username = this.mUsername.getParameterValue();
        
        String handle = Handle.FormatHandle(Username.PR_USER_HANDLE_NAMESPACE, username);
        
        return handle;
    }
    
    private void getHandles()
    throws Exception
    {
        this.mOperationalHandleName = this.getOperationalUserHandleName();
        this.mPracticeHandleName = this.getPracticeUserHandleName();
        
        this.mOperationalUserHandle = new Handle(this.mOperationalHandleName, this.mHandleApi);
        this.mPracticeUserHandle = new Handle(this.mPracticeHandleName, this.mHandleApi);
        
        this.authenticate();
    }
    
    /**
     * Determines if the contributors operational user Handle exists.
     * @return Returns true if the contributors operational user Handle exists and false if it does not.
     */
    public boolean getOperationalUserHandleExists()
    {
        return (this.mOperationalUserHandle != null);
    }
    
    /**
     * Determines if the contributors practice user Handle exists.
     * @return Returns true if the contributors practice user Handle exists and false if it does not.
     */
    public boolean getPracticeUserHandleExists()
    {
        return (this.mPracticeUserHandle != null);
    }
    
    /**
     * Determines if the provided credentials are authentic for the operational registry.
     * @return Returns true if the provided credentials match the secret key stored in the HS_SECKEY <CODE>HandleValue</CODE> at index 301 of the contributors operational user Handle. If the provided credentials are invalid returns false.
     */
    public boolean getAuthenticatedForOperational()
    {
        return this.mIsAuthenticatedForOperational;
    }
    
    /**
     * Determines if the provided credentials are authentic for the practice registry.
     * @return Returns true if the provided credentials match the secret key stored in the HS_SECKEY <CODE>HandleValue</CODE> at index 301 of the contributors practice user Handle. If the provided credentials are invalid returns false.
     */
    public boolean getAuthenticatedForPractice()
    {
        return this.mIsAuthenticatedForPractice;
    }
}
